/*
 * NyuObject.cpp
 *
 *  Created on: 2010/04/24
 *      Author: mitsu
 */

#include <iostream>
#include <cstdlib>
#include <cstring>
#include "NyuObject.hpp"
#include "NyuString.hpp"
#include "NyuGC.hpp"

NyuObject::NyuObject() :
        nslots_(0), slots_capacity_(1),
        slots_(static_cast<NyuValue*>(::malloc(sizeof(NyuValue)))) {
    addSlot(&NyuStaticString::PROTO, NYU_OBJECT_TO_VALUE(0), 0);
}

NyuObject::NyuObject(NyuObject* proto) :
        nslots_(0), slots_capacity_(1),
        slots_(static_cast<NyuValue*>(::malloc(sizeof(NyuValue)))) {
    addSlot(&NyuStaticString::PROTO, NYU_OBJECT_TO_VALUE(proto), 0);
}

NyuObject::~NyuObject() {
    //std::cout << __PRETTY_FUNCTION__ << ": " << this << std::endl;
    ::free(slots_);
}

NyuValue NyuObject::to_intval(NyuContext* c) {
    NyuValue v1 = getSlot(&NyuStaticString::INT, c);

    switch (NYU_VALUE_TAG(v1)) {
    case NYU_VALUE_TAG_INT:
        return v1;

    case NYU_VALUE_TAG_OBJECT:
        c->setCurrentTarget(v1);
        v1 = NYU_VALUE_UNTAG_OBJECT(v1)->getCall(c);
        break;

    case NYU_VALUE_TAG_NATIVE:
    case NYU_VALUE_TAG_BLOCK:
        break;
    }

    return c->callValue(v1);
}

NyuString* NyuObject::to_str(NyuContext* c) {
    char buf[32];
    ::snprintf(buf, sizeof(buf), "OBJECT(%p)", this);
    return new NyuImmutableString(::strdup(buf));
}

unsigned int NyuObject::hash() const {
    return static_cast<unsigned int>(reinterpret_cast<unsigned long>(this));
}

NyuValue NyuObject::getCall(NyuContext* c) {
    return getSlot(&NyuStaticString::CALL, c);
}

NyuValue NyuObject::opNot(NyuContext* c, bool is_tail) {
    NyuValue v1 = getSlot(&NyuStaticString::NOT, c);
    return c->callValue(v1, is_tail);
}

NyuValue NyuObject::opInvert(NyuContext* c, bool is_tail) {
    NyuValue v1 = getSlot(&NyuStaticString::INVERT, c);
    return c->callValue(v1, is_tail);
}

NyuValue NyuObject::opPos(NyuContext* c, bool is_tail) {
    NyuValue v1 = getSlot(&NyuStaticString::POS, c);
    return c->callValue(v1, is_tail);
}

NyuValue NyuObject::opNeg(NyuContext* c, bool is_tail) {
    NyuValue v1 = getSlot(&NyuStaticString::NEG, c);
    return c->callValue(v1, is_tail);
}

NyuValue NyuObject::opIncr(NyuContext* c, bool is_tail) {
    NyuValue v1 = getSlot(&NyuStaticString::INCR, c);
    return c->callValue(v1, is_tail);
}

NyuValue NyuObject::opDecr(NyuContext* c, bool is_tail) {
    NyuValue v1 = getSlot(&NyuStaticString::DECR, c);
    return c->callValue(v1, is_tail);
}

NyuValue NyuObject::opPostIncr(NyuContext* c, bool is_tail) {
    NyuValue v1 = getSlot(&NyuStaticString::POST_INCR, c);
    return c->callValue(v1, is_tail);
}

NyuValue NyuObject::opPostDecr(NyuContext* c, bool is_tail) {
    NyuValue v1 = getSlot(&NyuStaticString::POST_DECR, c);
    return c->callValue(v1, is_tail);
}

NyuValue NyuObject::opAdd(NyuValue other, NyuContext* c, bool is_tail) {
    NyuValue v1 = getSlot(&NyuStaticString::ADD, c);
    return c->callValue(v1, other, is_tail);
}

NyuValue NyuObject::opSub(NyuValue other, NyuContext* c, bool is_tail) {
    NyuValue v1 = getSlot(&NyuStaticString::SUB, c);
    return c->callValue(v1, other, is_tail);
}

NyuValue NyuObject::opMul(NyuValue other, NyuContext* c, bool is_tail) {
    NyuValue v1 = getSlot(&NyuStaticString::MUL, c);
    return c->callValue(v1, other, is_tail);
}

NyuValue NyuObject::opDiv(NyuValue other, NyuContext* c, bool is_tail) {
    NyuValue v1 = getSlot(&NyuStaticString::DIV, c);
    return c->callValue(v1, other, is_tail);
}

NyuValue NyuObject::opMod(NyuValue other, NyuContext* c, bool is_tail) {
    NyuValue v1 = getSlot(&NyuStaticString::MOD, c);
    return c->callValue(v1, other, is_tail);
}

NyuValue NyuObject::opLShift(NyuValue other, NyuContext* c, bool is_tail) {
    return 0;
}

NyuValue NyuObject::opRShift(NyuValue other, NyuContext* c, bool is_tail) {
    return 0;
}

NyuValue NyuObject::opLt(NyuValue other, NyuContext* c, bool is_tail) {
    return 0;
}

NyuValue NyuObject::opLe(NyuValue other, NyuContext* c, bool is_tail) {
    return 0;
}

NyuValue NyuObject::opGt(NyuValue other, NyuContext* c, bool is_tail) {
    return 0;
}

NyuValue NyuObject::opGe(NyuValue other, NyuContext* c, bool is_tail) {
    return 0;
}

NyuValue NyuObject::opOr(NyuValue other, NyuContext* c, bool is_tail) {
    return 0;
}

NyuValue NyuObject::opXor(NyuValue other, NyuContext* c, bool is_tail) {
    return 0;
}

NyuValue NyuObject::opAnd(NyuValue other, NyuContext* c, bool is_tail) {
    return 0;
}

NyuValue NyuObject::opEq(NyuValue other, NyuContext* c, bool is_tail) {
    return NYU_INT_TO_VALUE(NYU_OBJECT_TO_VALUE(this) == other);
}

NyuValue NyuObject::opNe(NyuValue other, NyuContext* c, bool is_tail) {
    return 0;
}

NyuValue NyuObject::opIAdd(NyuValue other, NyuContext* c, bool is_tail) {
    return 0;
}

NyuValue NyuObject::opISub(NyuValue other, NyuContext* c, bool is_tail) {
    return 0;
}

NyuValue NyuObject::revEqStr(const char* str, NyuContext* c, bool is_tail) {
    return 0;
}

NyuValue NyuObject::opIAddSlot(NyuObject* name, NyuValue val, NyuContext* c,
                               bool is_tail) {
    unsigned int idx;

    if (slot_tbl_.get(name, idx, c)) {
        switch (NYU_VALUE_TAG2(slots_[idx], val)) {
        case NYU_VALUE_TAG_INT_INT:
            return slots_[idx] += val;

        default:
            std::cout << "Sorry! currently IAddSlot only supports int, int" << std::endl;
            break;
        }
    } else {
        // TODO 例外
    }

    return 0;
}

NyuValue NyuObject::opISubSlot(NyuObject* name, NyuValue val, NyuContext* c,
                               bool is_tail) {
    unsigned int idx;

    if (slot_tbl_.get(name, idx, c)) {
        switch (NYU_VALUE_TAG2(slots_[idx], val)) {
        case NYU_VALUE_TAG_INT_INT:
            return slots_[idx] -= val;

        default:
            std::cout << "Sorry! currently ISubSlot only supports int, int" << std::endl;
            break;
        }
    } else {
        // TODO 例外
    }

    return 0;
}

void NyuObject::putSlot(NyuObject* name, NyuValue value, NyuContext* c) {
    unsigned int idx;

    if (slot_tbl_.get(name, idx, c)) {
        slots_[idx] = value;
    } else {
        addSlot(name, value, c);
    }
}

NyuValue NyuObject::getSlot(NyuObject* name, NyuContext* c) {
    unsigned int idx;

    if (slot_tbl_.get(name, idx, c))
        return slots_[idx];

    NyuObject* proto = NYU_VALUE_UNTAG_OBJECT(slots_[0]);
    if (!proto)
        return 0;

    return proto->getSlot(name, c);
}

void NyuObject::addSlot(NyuObject* name, NyuValue value, NyuContext* c) {
    if (slots_capacity_ <= nslots_) {
        slots_capacity_ *= 2;
        slots_ = static_cast<NyuValue*>(
                ::realloc(slots_, sizeof(NyuValue) * slots_capacity_));
    }

    slot_tbl_.put(name, nslots_, c);
    slots_[nslots_++] = value;
}

NyuValue NyuObject::getitem(NyuValue idx, NyuContext* c) {
    // TODO
    return 0;
}

void NyuObject::putitem(NyuValue idx, NyuValue val, NyuContext* c) {
    // TODO
}

NyuValue NyuObject::getLength(NyuContext* c) {
    return 0;
}

NyuValue NyuObject::push(NyuContext* c) {
    return 0;
}

NyuValue NyuObject::pop(NyuContext* c) {
    return 0;
}

namespace {

struct KeyCopyFn {
    bool* has_new_obj_;
    KeyCopyFn(bool* has_new_obj) : has_new_obj_(has_new_obj) {}
    void operator()(NyuObject*& key) {
        key = NyuGC::copy(key, has_new_obj_);
    }
};

}

void NyuObject::copyChildren(bool* has_new_obj) {
    for (int i = 0, j = nslots_; i < j; i++) {
        if (!NYU_VALUE_IS_OBJECT(slots_[i]))
            continue;

        NyuObject* obj = NYU_VALUE_UNTAG_OBJECT(slots_[i]);
        slots_[i] = NYU_OBJECT_TO_VALUE(NyuGC::copy(obj, has_new_obj));
    }

    slot_tbl_.eachKeys(KeyCopyFn(has_new_obj));
}

void* NyuObject::operator new(size_t size) {
    //std::cout << __PRETTY_FUNCTION__ << ": " << size << std::endl;
    //return ::operator new(size);
    return NyuGC::allocate(size);
    //void* p = NyuGC::allocate(size);
    //std::cout << __PRETTY_FUNCTION__ << ": " << size << ", " << p << std::endl;
    //return p;
}

void* NyuObject::operator new(size_t size, void* p) {
    return p;
}
